#ifndef BGV_SCHEME_H_
#define BGV_SCHEME_H_

#include "ciphertext.h"
#include "common.h"
#include "context.h"
#include "key.h"
#include "numb.h"
#include "plaintext.h"
#include "secret_key.h"
#include <chrono>
#include <cstring>
#include <eigen3/Eigen/Dense>
#include <eigen3/Eigen/Sparse>
#include <map>

using namespace std;

static long ENCRYPTION = 0;
static long MULTIPLICATION = 1;

using namespace Eigen;
using namespace std;

/* *
 * @brief BGV全同态加密方案的主要类，包含加密、解密及同态操作的实现。
 */
class Scheme {
public:
    Context &context;
    map<long, Key> keyMap;
    map<long, SwitchKey> leftRotKeyMap;
    map<long, SwitchKey> switchKeyMap;

    Key encKey;
    SwitchKey multKey;
    SwitchKey rotKey;
    uint64_t *ex;
    Scheme(Context &context);
    Scheme(SecretKey &secretKey, Context &context);

    uint64_t *buffQ;
    uint64_t *buff;
    uint64_t *nttIndex;
    uint64_t *sx;

    void AddEncKey(SecretKey &secretKey);
    void AddMultKey(SecretKey &secretKey);
    void AddLeftRotKey(SecretKey &secretKey, long rot);
    void AddLeftRotKeys(SecretKey &secretKey);
    void AddRightRotKeys(SecretKey &secretKey);
    Ciphertext EncryptMsg(SecretKey &secretkey, Plaintext &message);
    Ciphertext EncryptMsg(Plaintext &message);
    Plaintext DecryptMsg(SecretKey &secretKey, Ciphertext &cipher);
    Ciphertext Negate(Ciphertext &cipher);
    void NegateAndEqual(Ciphertext &cipher);
    Ciphertext Add(Ciphertext &cipher1, Ciphertext &cipher2);
    void Add(Ciphertext &res, Ciphertext&cipher1, Ciphertext &cipher2);
    void AddAndEqual(Ciphertext &cipher1, Ciphertext &cipher2);
    Ciphertext Sub(Ciphertext &cipher1, Ciphertext &cipher2);
    void SubAndEqual(Ciphertext &cipher1, Ciphertext &cipher2);
    void Sub2AndEqual(Ciphertext &cipher1, Ciphertext &cipher2);

    void GadgetProduct(int levelQ, int levelP, uint64_t *cx, SwitchKey &gadgetCiphertext, uint64_t *ax, uint64_t *bx);
    void GadgetProductSinglePAndBitDecompNoModDown(int levelQ, int levelP, uint64_t *cx, SwitchKey &gadgetCt,
        uint64_t *tmpAx, uint64_t *tmpBx);

    Ciphertext Mult(Ciphertext &cipher1, Ciphertext &cipher2);
    void Mult(Ciphertext &res, Ciphertext &cipher1, Ciphertext &cipher2);

    void MultConstAndEqual(Ciphertext &cipher, double cnst);
    void LeftRotateAndEqualFast(Ciphertext &cipher, long rotSlots);
    void LeftRotateByPo2AndEqual(Ciphertext &cipher, long logRotSlots);
    void RightRotateByPo2AndEqual(Ciphertext &cipher, long logRotSlots);
    Ciphertext LeftRotate(Ciphertext &cipher, long rotSlots);
    void LeftRotateAndEqual(Ciphertext &cipher, long rotSlots);
    Ciphertext RightRotate(Ciphertext &cipher, long rotSlots);
    void RightRotateAndEqual(Ciphertext &cipher, long rotSlots);
    Ciphertext MultPt(Ciphertext &cipher, Plaintext &plaintext);
    Ciphertext AddPt(Ciphertext &cipher, Plaintext &plaintext);
    void AddPt(Ciphertext &res, Ciphertext &cipher, Plaintext &plaintext);
    Ciphertext Encrypt(uint64_t *vals, long slots, long l);
    Ciphertext EncryptBySk(uint64_t *vals, long slots, long l, SecretKey &secretkey);
    Plaintext Encode(uint64_t *v, long slots, long l);
    uint64_t *Decrypt(SecretKey &secretKey, Ciphertext &cipher);
    uint64_t *Decode(Plaintext &msg);
};

#endif
